#include "CCPNetwork.h"

#define SOCKET_READ_TIMEOUT_MS 50
#define SOCKET_WRITE_TIMEOUT_MS 50

int ccpread(NETWORK_S *n, unsigned char *buffer, int len, int timeout_ms)
{
    int rc;
    int ret;
    INT32 lefttime;
    INT32 recvlen = 0;
    ALIYUN_NETWORK_FD_SET_S fdset;
    ALIYUN_IOT_TIME_TYPE_S endTime;
    ALIYUN_IOT_TIME_TYPE_S tv;
    ALIYUN_IOT_TIME_TYPE_S interval;

    aliyun_iot_timer_init(&endTime);
    aliyun_iot_timer_init(&tv);
    aliyun_iot_timer_init(&interval);
    aliyun_iot_timer_cutdown(&endTime, timeout_ms);

    do
    {
        aliyun_iot_network_FD_ZERO(&fdset);
        aliyun_iot_network_FD_SET(n->socketFd, &fdset);

        lefttime = aliyun_iot_timer_remain(&endTime);
        if (lefttime <= 0)
        {
            WRITE_IOT_ERROR_LOG("ccp read timeout!");
            return -1;
        }

        aliyun_iot_timer_assignment(lefttime, &tv);

        ret = aliyun_iot_network_select(n->socketFd + 1, &fdset, NULL, NULL, &tv);
        if (ret < 0)
        {
            if (NETWORK_SIGNAL_INTERRUPT == ret)
            {
                continue;
            }

            WRITE_IOT_ERROR_LOG("ccp read select failed ret = %d!", ret);
            return -1;
        }
        else if (0 == ret)
        {
            WRITE_IOT_ERROR_LOG("ccp read select timeout!");
            return -1;
        }
        else if (1 == ret)
        {
            if (0 == aliyun_iot_network_FD_ISSET(n->socketFd, &fdset))
            {
                WRITE_IOT_ERROR_LOG("another fd readable!");
                continue;
            }

            aliyun_iot_timer_cutdown(&interval, SOCKET_READ_TIMEOUT_MS);
            aliyun_iot_network_setsockopt(n->socketFd, SOL_SOCKET_IOT, SO_RCVTIMEO_IOT, &interval, sizeof(ALIYUN_IOT_TIME_TYPE_S));

            rc = aliyun_iot_network_recv(n->socketFd, buffer + recvlen, len - recvlen, 0);
            if (rc > 0)
            {
                recvlen += rc;
            }
            else if (0 == rc)
            {
                WRITE_IOT_ERROR_LOG("connection is closed by peer, recvlen = %d!", recvlen);
                return -1;
            }
            else
            {
                int err = aliyun_iot_get_errno();
                if ((EINTR_IOT == err) || (EWOULDBLOCK_IOT == err) || (EAGAIN_IOT == err))
                {
                    continue;
                }
                else
                {
                    WRITE_IOT_ERROR_LOG("ccp read failed, rc = %d, err = %d, recvlen = %d", rc, err, recvlen);
                    return -1;
                }
            }
        }
    }while(recvlen < len);

    return recvlen;
}

int ccpwrite(NETWORK_S *n, unsigned char *buffer, int len, int timeout_ms)
{
    int rc;
    int ret;
    INT32 lefttime;
    INT32 sendlen = 0;
    ALIYUN_NETWORK_FD_SET_S fdset;
    ALIYUN_IOT_TIME_TYPE_S endTime;
    ALIYUN_IOT_TIME_TYPE_S tv;
    ALIYUN_IOT_TIME_TYPE_S interval;

    aliyun_iot_timer_init(&endTime);
    aliyun_iot_timer_init(&tv);
    aliyun_iot_timer_init(&interval);
    aliyun_iot_timer_cutdown(&endTime, timeout_ms);

    do
    {
        aliyun_iot_network_FD_ZERO(&fdset);
        aliyun_iot_network_FD_SET(n->socketFd, &fdset);

        lefttime = aliyun_iot_timer_remain(&endTime);
        if (lefttime <= 0)
        {
            WRITE_IOT_ERROR_LOG("ccp write timeout!");
            return -1;
        }

        aliyun_iot_timer_assignment(lefttime, &tv);

        ret = aliyun_iot_network_select(n->socketFd + 1, NULL, &fdset, NULL, &tv);
        if (ret < 0)
        {
            if (NETWORK_SIGNAL_INTERRUPT == ret)
            {
                continue;
            }

            WRITE_IOT_ERROR_LOG("ccp write select failed, ret = %d!", ret);
            return -1;
        }
        else if (0 == ret)
        {
            WRITE_IOT_ERROR_LOG("ccp write select timeout!");
            return -1;
        }
        else if (1 == ret)
        {
            if (0 == aliyun_iot_network_FD_ISSET(n->socketFd, &fdset))
            {
                WRITE_IOT_ERROR_LOG("another fd writeable!");
                continue;
            }

            aliyun_iot_timer_cutdown(&interval, SOCKET_WRITE_TIMEOUT_MS);
            aliyun_iot_network_setsockopt(n->socketFd, SOL_SOCKET_IOT, SO_SNDTIMEO_IOT, &interval, sizeof(ALIYUN_IOT_TIME_TYPE_S));

            rc = aliyun_iot_network_write(n->socketFd, buffer, len);
            if (rc > 0)
            {
                sendlen += rc;
            }
            else
            {
                int err = aliyun_iot_get_errno();
                if ((EINTR_IOT == err) || (EWOULDBLOCK_IOT == err) || (EAGAIN_IOT == err))
                {
                    continue;
                }
                else
                {
                    WRITE_IOT_ERROR_LOG("ccp write failed, rc = %d, err = %d, sendlen = %d!", rc, err, sendlen);
                    return -1;
                }
            }
        }
    }while(sendlen < len);

    return sendlen;
}

void disconnect(NETWORK_S *n)
{
    aliyun_iot_network_close(n->socketFd);
}

void newNetwork(NETWORK_S *n)
{
	n->socketFd = 0;
	n->ccpread = ccpread;
	n->ccpwrite = ccpwrite;
	n->disconnect = disconnect;
}

int connectNetwork(NETWORK_S *n, char *addr, int port)
{
    int rc;
	int type = SOCK_STREAM_IOT;
	INT32 family = AF_INET_IOT;
	ALIYUN_NETWORK_ADDRINFO_S *result = NULL;
	ALIYUN_NETWORK_ADDRINFO_S  hints = {0, AF_UNSPEC_IOT, SOCK_STREAM_IOT, IPPROTO_TCP_IOT, 0, NULL, NULL, NULL};
    ALIYUN_NETWORK_SOCKADDR_IN_S address;

    memset(&address, 0, sizeof(address));

    newNetwork(n);

	if (0 == (rc = aliyun_iot_network_getaddrinfo(addr, NULL, &hints, &result)))
	{
	    ALIYUN_NETWORK_ADDRINFO_S *res = result;

		/* prefer ip4 addresses */
		while (res)
		{
			if (AF_INET_IOT == res->ai_family)
			{
				result = res;
				break;
			}
			res = res->ai_next;
		}

		if (AF_INET_IOT == result->ai_family)
		{
			address.sin_port = htons(port);
			address.sin_family = family;
			address.sin_addr = ((ALIYUN_NETWORK_SOCKADDR_IN_S *) (result->ai_addr))->sin_addr;
		}
		else
		{
		    WRITE_IOT_ERROR_LOG("aliyun_iot_network_getaddrinfo error!");
			rc = -1;
		}

		aliyun_iot_network_freeaddrinfo(result);
	}
	else
	{
	    WRITE_IOT_ERROR_LOG("aliyun_iot_network_getaddrinfo error, rc = %d!", rc);
	}

	if (0 == rc)
	{
		n->socketFd = aliyun_iot_network_socket(family, type, IPPROTO_TCP_IOT);
		if (n->socketFd >= 0)
		{
            WRITE_IOT_INFO_LOG("connecting!");
			rc = aliyun_iot_network_connect(n->socketFd, (ALIYUN_NETWORK_SOCKADDR_IN_S *) &address, sizeof(address));
			if (0 != rc)
			{
			    WRITE_IOT_ERROR_LOG("connect failed, rc = %d!", rc);
			}
		}
		else
		{
		    WRITE_IOT_ERROR_LOG("aliyun_iot_network_socket error, n->socketFd = %d!", n->socketFd);
		}
	}

	return rc;
}

